#include "include/lua.hpp"

LuaWrapper::LuaWrapper(State *s, const std::string &n, const std::string &fileName)
{
	st = s;
	name = "Lua State '" + n + "'";
	
	l = lua_open();
	luaL_openlibs(l);
	
	if( fileName != "" )
	{
		//load the chunk at fileName
		chunkStruct *c = loadChunk(fileName.c_str(), st);
		runChunk(c);
		delete c;
	}

}

chunkStruct* LuaWrapper::loadChunk (const char *fileName, State *st)
{
	// process groked from luac.c
	
	lua_State* L;
	L = lua_open();
	if( !L ) {
		st->m(LOG_ERROR, "Not enough memory to open lua state for file '%s'.\n", fileName);
		exit(-50);
	}
	
	// use new here so we can invoke convenient constructors :)
	chunkStruct *ret = new chunkStruct(fileName);
	
	if( lua_cpcall(L,process,ret) )
	{
		st->m(LOG_DEBUG, "Error loading script '%s': \n", fileName);
		st->m(LOG_ERROR, "\t%s\n", lua_tostring(L, -1));
		return 0;
	}
	
	// if size == 0 then there was nothing in the file we loaded 
	if( ret->size == 0 )
	{
		st->m(LOG_WARNING, "Lua script '%s' is empty.\n", fileName);
		ret->target=NULL;
	}
	
	lua_close(L);
	
	return ret;
}

int LuaWrapper::process(lua_State *L)
{
	// process groked from luac.c

	struct chunkStruct *chunk = (struct chunkStruct*)lua_touserdata(L,1);

	const Proto* f;

	int stripping = LUA_COMPILER_STRIPPING;

	if (luaL_loadfile(L, chunk->fileName))
		return -1;

	// from #define toproto in luac.c
	// as far as i understand this picks off the top function given to us
	f = (clvalue(L->top+(-1))->l.p);

	FILE *D= fopen(LUA_COMPILER_TMP, "wb");

	lua_lock(L);
	luaU_dump(L, f, writer, D, stripping);
	lua_unlock(L);

	freopen(LUA_COMPILER_TMP, "rb", D);
	fseek(D, 0, SEEK_END);
	chunk->size = ftell(D);
	rewind(D);
	chunk->target = (char *)malloc( sizeof(char) * (chunk->size) );
	if(!(chunk->target)) exit(99);
	fread( chunk->target, 1, chunk->size, D);

	if( ferror(D) ) exit(100);
	if( fclose(D) ) exit(101);

	return 0;
}

int LuaWrapper::writer( lua_State *L, const void* p, size_t size, void *u )
{
	UNUSED(L);
	return ( fwrite(p, size, 1, (FILE*)u) !=1 ) && (size!=0) ;
}

int LuaWrapper::runChunk( chunkStruct *chunk )
{
	int ret = 0;
	
	st->m(LOG_DEBUG, "Running lua chunk '%s'... \n", chunk->fileName);
	
	ret += luaL_loadbuffer(l, chunk->target, chunk->size, chunk->fileName);
	ret += lua_pcall(l, 0, 0, 0 );
	
	if(ret)
	{
		st->m(LOG_ERROR, "File '%s' execution failed.\n", chunk->fileName);
		
		// get error from lua state and log it
		st->m(LOG_ERROR, "%s\n", lua_tostring(l, -1));
		lua_pop(l, 1);  /* pop error message from the stack */
	}
	
	return ret;
}

LuaWrapper::~LuaWrapper()
{
	lua_close(l);
}

int LuaWrapper::getGlobalInt( const char *name, int &var )
{
	lua_getglobal(l, name);
	if( lua_isnil(l, -1) )
	{
		st->m(LOG_WARNING, "Value '%s' is not set.\n", name );
		return -1;
	}
	
	if( !lua_isnumber(l, -1) )
	{
		st->m(LOG_ERROR, "Expected '%s' to be a number (int).\n", name );
		return -2;
	}
	
	var = (int)lua_tonumber(l, lua_gettop(l));
	lua_pop(l, 1);
	
	return 0;
}

int LuaWrapper::getGlobalString( const char *name, std::string &str )
{
	lua_getglobal(l, name);
	
	if( lua_isnil(l, -1) )
	{
		st->m(LOG_WARNING, "Value '%s' is not set.\n", name );
		return -1;
	}
	
	if( !lua_isstring(l, -1) )
	{
		st->m(LOG_ERROR, "Expected '%s' to be a string.\n", name );
		return -2;
	}
	
	str = lua_tostring(l, -1);
	lua_pop(l, 1);
	
	return 0;
}

int LuaWrapper::getGlobalBool( const char *name, bool &b)
{
	lua_getglobal(l, name);
	
	if( lua_isnil(l, -1) )
	{
		st->m(LOG_WARNING, "Value '%s' is not set.\n", name );
		return -1;
	}
	
	if( !lua_isboolean(l, -1) )
	{
		st->m(LOG_ERROR, "Expected '%s' to be a boolean value..\n", name );
		return -2;
	}
	
	b = (bool)lua_toboolean(l, -1);
	lua_pop(l, 1);
	
	return 0;
}

int LuaWrapper::getStringArray( const char *name, int max, std::string **str )
{
	int size = 0;
	int tableIndex;
	
	lua_getglobal(l, name);
	if( lua_isnil(l, -1))
	{
		st->m(LOG_WARNING, "Value '%s' is not set.\n", name );
		return -1;
	}
	
	if( !lua_istable(l, -1) )
	{
		st->m(LOG_ERROR, "Expected '%s' to be a table.\n", name );
		return -2;
	}
	
	// mark the index of the table
	tableIndex = lua_gettop(l);
	
	// push the first key
	lua_pushnil(l);
	
	//traverse table
	while (lua_next(l, tableIndex))
	{
		// check to make sure its a string
		if( !lua_isstring(l, -1) )
		{
			st->m(LOG_ERROR, "Expected element %i of table '%s' to be a string.\n", size, name );
			return -3;
		}
		if( !strlen(lua_tostring(l, -1)) )
		{
			st->m(LOG_ERROR, "Element %i of table '%s' is empty.\n", size, name );
			return -4;
		}
		
		//pop and incriment
		lua_pop(l, 1);
		size++;
	}
	
	// make sure the table isn't empty
	if( !size )
	{
		st->m(LOG_ERROR, "Table '%s' is empty.\n", name );
		return -5;
	}
	
	// are we trying to fill an array?
	if( max > 0 )
	{
		// make sure we're under the requested amount
		if( size > max )
		{
			st->m(LOG_ERROR, "Table '%s' has too many elements (max %i).\n", name, max );
			return -6;
		}
	}
	
	// allocate space
	*str = new std::string[size];
	
	// push the first key on top
	lua_pushnil(l);
	
	// traverse again, but copy values out
	for( int i = 0; i < size; i++ )
	{
		lua_next(l, tableIndex);
		if( !lua_isnil(l, -1) )
			*str[i] = lua_tostring(l, -1);
		else
			*str[i] = "<string missing>";
		
		//pop
		lua_pop(l, 1);
	}
	
	//pop table
	lua_pop(l, 1);
	
	return size;
}

int LuaWrapper::getIntArray( const char *name, int max, int **var )
{
	int size = 0;
	int tableIndex;
	
	lua_getglobal(l, name);
	if( lua_isnil(l, -1))
	{
		st->m(LOG_WARNING, "Value '%s' is not set.\n", name );
		return -1;
	}
	
	if( !lua_istable(l, -1) )
	{
		st->m(LOG_ERROR, "Expected '%s' to be a table.\n", name );
		return -2;
	}
	
	// mark the index of the table
	tableIndex = lua_gettop(l);
	
	// push the first key
	lua_pushnil(l);
	
	//traverse table
	while (lua_next(l, tableIndex))
	{
		// check to make sure its a string
		if( !lua_isnumber(l, -1) )
		{
			st->m(LOG_ERROR, "Expected element %i of table '%s' to be a number.\n", size, name );
			return -3;
		}
		
		//pop and incriment
		lua_pop(l, 1);
		size++;
	}
	
	// make sure the table isn't empty
	if( !size )
	{
		st->m(LOG_ERROR, "Table '%s' is empty.\n", name );
		return -5;
	}
	
	// are we trying to fill an array?
	if( max > 0 )
	{
		// make sure we're under the requested amount
		if( size > max )
		{
			st->m(LOG_ERROR, "Table '%s' has too many elements (max %i).\n", name, max );
			return -6;
		}
	}
	
	// allocate space
	*var = new int[size];
	
	// push the first key on top
	lua_pushnil(l);
	
	// traverse again, but copy values out
	for( int i = 0; i < size; i++ )
	{
		lua_next(l, tableIndex);
		if( !lua_isnil(l, -1) )
			*var[i] = (int)lua_tonumber(l, -1);
		else
			*var[i] = -1;
		
		//pop
		lua_pop(l, 1);
	}
	
	//pop table
	lua_pop(l, 1);
	
	return size;
}

int LuaWrapper::getGlobalUShort( const char *name, unsigned short &var )
{
	
	lua_getglobal(l, name);
	if( lua_isnil(l, -1) )
	{
		st->m(LOG_WARNING, "Value '%s' is not set.\n", name );
		return -1;
	}
	
	if( !lua_isnumber(l, -1) )
	{
		st->m(LOG_ERROR, "Expected '%s' to be a number (unsigned short).\n", name );
		return -2;
	}
	
	var = (unsigned short)lua_tonumber(l, lua_gettop(l));
	lua_pop(l, 1);
	
	return 0;
}


